package top.tenebrous.yzl.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.tenebrous.yzl.basic.model.CtsResult;
import top.tenebrous.yzl.entity.TestNo;
import top.tenebrous.yzl.entity.TestNoOffset;
import top.tenebrous.yzl.entity.TestOrgInfo;
import top.tenebrous.yzl.mapper.TestNoMapper;
import top.tenebrous.yzl.mapper.TestNoOffsetMapper;
import top.tenebrous.yzl.mapper.TestOrgInfoMapper;
import top.tenebrous.yzl.model.dto.TestNoDTO;
import top.tenebrous.yzl.service.ITestOrgInfoService;
import top.tenebrous.yzl.utils.RedisLock;
import top.tenebrous.yzl.utils.SnowFlakeUtil;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

/**
 * @author yazhang6
 */
@Slf4j
@Service
public class TestOrgInfoServiceImpl implements ITestOrgInfoService {

    private final static String AREA_PREFIX = "340";

    private final static String[] UNQION_AREA_CODE = new String[] {"1100", "1200", "1300", "1400", "1500", "1600", "1700", "1800", "1900", "2020"};

    private final static String PARENT_CODE = "00";

    @Autowired
    private TestOrgInfoMapper testOrgInfoMapper;

    @Resource
    private RedisTemplate<String, Long> redisTemplate;

    private final static String REDIS_KEY_PREFIX = "IMA-PUBLIC-HEALTH:";

    private final static Long DEFALUT_NUM = 100L;

    @Autowired
    private TestNoMapper testNoMapper;

    @Autowired
    private TestNoOffsetMapper testNoOffsetMapper;

    @Override
    public CtsResult<List<String>> createOrgInfo() {
        Random random = new Random();
        List<String> codes = new ArrayList<>();
        for (String code : UNQION_AREA_CODE) {
            // 生成父区号
            String parentCode = generation(AREA_PREFIX, code, PARENT_CODE, random.nextInt(900) + 100+"");
            codes.add(parentCode);
        }
        return CtsResult.success(codes);
    }

    @Override
    public List<TestOrgInfo> selectAreaCode() {
        return testOrgInfoMapper.selectList(new LambdaQueryWrapper<TestOrgInfo>().eq(TestOrgInfo::getIsOnline, "0"));
    }

    /**
     * 扩容的目的，是为了新生成一个key，从头存储bit位的值，那么，就需要有一个算法，重新计算索引位置
     * 比如:
     * start:    132
     * end:      224
     * last_end: 118
     *
     * 那起始位置，应该是 119  ~ 224
     *
     *  0    1    2    3    4    5   ...  105
     * 119  120  121  122  123  124  ...  224
     *
     * 综上所述：
     * index = end - (lase_end + 1)
     *
     * @param areaCode
     * @param offset
     */
    @Async("localBootAsyncExecutor")
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void resize(String areaCode, TestNoOffset offset) {
        // 查询档案数据
        List<TestNoDTO> codes = testNoMapper.selectCodeByAreaCode(areaCode, DEFALUT_NUM, offset.getEndIncrVal() + 1);
        // 生成key 从上一个自增位 + 1  开始（包含）
        Long startIncr = offset.getEndIncrVal() + 1;
        Long endIncr = codes.get(codes.size() - 1).getIncrNum();
        RedisLock redisLock = new RedisLock(redisTemplate, REDIS_KEY_PREFIX + "RESIZE_LOCK:" + startIncr + ":" + endIncr);

        try {
            if (!redisLock.tryLock(1000)) {
                log.info("获取锁失败....");
            } else {

                String bitKey = REDIS_KEY_PREFIX + areaCode + ":" + startIncr + ":" + endIncr;          // offsetNum应为id值
                if (Boolean.TRUE.equals(redisTemplate.hasKey(bitKey))) {
                    log.info("key:[{}] 已重设", bitKey);
                    return;
                }

                // 设置bit
                for (TestNoDTO testNo : codes) {
                    // 计算索引位
                    long lastEndVal = startIncr;   // 起始位置 = 上一个最大自增位 + 1
                    long index = testNo.getIncrNum() - lastEndVal;
                    redisTemplate.opsForValue().setBit(bitKey, index, true);
                    log.info("bit 重设code:[{}]  index:[{}]", JSON.toJSONString(testNo), index);
                }
                // 保存偏移
                offset.setNextActivityId(codes.get(codes.size() - 1).getId());
                offset.setNextStartIncrVal(startIncr);
                offset.setNextEndIncrVal(endIncr);
                offset.setUpdateDate(new Date());
                testNoOffsetMapper.updateById(offset);
                log.info("bit map resize success , key:[{}]  offset:{}", bitKey, JSON.toJSONString(offset));
            }
        } catch (Exception e) {
            log.error("rediskey 重设失败:{}", e);
        }finally {
            redisLock.unlock();
        }

    }

    /**
     * 生成父区号
     *
     * @param areaPrefix
     * @param code
     * @param parentCode
     * @return
     */
    private String generation(String areaPrefix, String code, String parentCode, String random) {
        String parent = areaPrefix + code + parentCode + random;
        saveDB(parent);
        return parent;
    }

    private void saveDB(String parent) {
        for (int i = 0; i < 2; i++) {
            TestOrgInfo orgInfo = new TestOrgInfo();
            orgInfo.setAreaCode(parent);
            orgInfo.setOrgId(SnowFlakeUtil.getDefaultSnowFlakeId());
            orgInfo.setCreateDate(new Date());
            orgInfo.setUpdateDate(new Date());
            testOrgInfoMapper.insert(orgInfo);
        }

    }
}
